package jrain.fw.cfg.db;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicBoolean;

import com.alibaba.druid.filter.Filter;
import com.alibaba.druid.filter.logging.Slf4jLogFilter;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.pool.DruidDataSourceFactory;

import jrain.fw.core.business.BusinessConst;
import jrain.fw.core.cfg.CfgCallback;
import jrain.fw.core.cfg.CfgService;
import jrain.fw.core.consts.Consts;
import jrain.fw.core.dao.handler.IResultSetHandler;
import jrain.fw.core.log.Logger;
import jrain.fw.core.log.LoggerFactory;
import jrain.fw.core.service.DestroyService;
import jrain.fw.core.service.InitService;
import jrain.fw.core.service.Service;
import jrain.fw.core.service.ServiceConst;
import jrain.fw.core.utils.FwUtils;
import jrain.fw.dao.common.handler.RowMapHandler;
import jrain.fw.dao.common.handler.RowMapListHandler;
import jrain.utils.collection.RDMap;
import jrain.utils.collection.RMap;
import jrain.utils.consts.BaseConst;
import jrain.utils.exception.ServiceException;
import jrain.utils.lang.LangUtils;
import jrain.utils.lang.StringUtils;
import jrain.utils.sql.SqlUtils;

/**
 * <pre>
 * 作者：3244924214@qq.com 
 * 描述：配置存放在数据库中.
 * </pre>
 */
@Service(name = "db", group = ServiceConst.S_CFG_SERVICE, module = ServiceConst.M_CFG, dependency = ServiceConst.S_JSON_SERVICE)
public class CfgDBService implements CfgService, InitService, DestroyService {

	private static final Logger log = LoggerFactory.getLogger(CfgDBService.class);
	private volatile DruidDataSource druidDataSource = null;
	private static volatile ConcurrentMap<String, Object> sqlMap = new ConcurrentHashMap<>();
	private static volatile ConcurrentMap<String, Object> paramMap = new ConcurrentHashMap<>();
	private static volatile ConcurrentMap<String, Object> validateMap = new ConcurrentHashMap<>();
	private static volatile ConcurrentMap<String, Map<String, Object>> propMap = new ConcurrentHashMap<>();
	private volatile boolean isDebug = false;
	private static final AtomicBoolean inited = new AtomicBoolean(false);

	@Override
	public RMap<String, Object> getCfgMap(String module) {
		RMap<String, Object> rsMap = getCfgMap(module, null);
		if (rsMap == null) {
			throw new ServiceException("Configuration module cannot be empty! module=" + module);
		}
		return rsMap;
	}

	@Override
	public RMap<String, Object> getCfgMap(String module, RMap<String, Object> defaultMap) {
		if (Consts.CFG_TYPE_SQL.equals(module)) {
			if (isDebug) {
				loadSql();
			}
			return new RDMap<>(sqlMap);
		} else if (Consts.CFG_TYPE_PARAM.equals(module)) {
			if (isDebug) {
				loadParam();
			}
			return new RDMap<>(paramMap);
		} else if (Consts.CFG_TYPE_VALIDATE.equals(module)) {
			if (isDebug) {
				loadValidate();
			}
			return new RDMap<>(validateMap);
		} else {
			if (isDebug) {
				loadProp(module);
			}
			Map<String, Object> rsMap = propMap.get(module);
			if (rsMap == null) {
				return defaultMap;
			}
			return new RDMap<>(rsMap);
		}

	}

	@Override
	public Object getCfgValue(String module, String key) {
		Object obj = getCfgValue(module, key, null);
		return obj;
	}

	@Override
	public Object getCfgValue(String module, String key, Object dvalue) {
		if (Consts.CFG_TYPE_SQL.equals(module)) {
			if (isDebug) {
				loadSql(key);
			}
			Object obj = sqlMap.get(key);
			if (obj == null) {
				obj = dvalue;
			}
			return obj;
		} else if (Consts.CFG_TYPE_PARAM.equals(module)) {
			if (isDebug) {
				loadParam(key);
			}
			Object obj = paramMap.get(key);
			if (obj == null) {
				obj = dvalue;
			}
			return obj;
		} else if (Consts.CFG_TYPE_VALIDATE.equals(module)) {
			if (isDebug) {
				loadValidate(key);
			}
			Object obj = validateMap.get(key);
			if (obj == null) {
				obj = dvalue;
			}
			return obj;
		} else {
			if (isDebug) {
				loadProp(module);
			}
			Map<String, Object> map = propMap.get(module);
			if (map == null) {
				return dvalue;
			} else {
				Object obj = map.get(key);
				if (obj == null) {
					obj = dvalue;
				}
				return obj;
			}
		}
	}

	@Override
	public void setCfgValue(String module, RMap<String, Object> dataMap) {

	}

	@Override
	public void registerCfgCallBack(String module, CfgCallback callback) {

	}

	@Override
	public void unregisterCfgCallBack(String module) {

	}

	@Override
	public void unregisterCfgCallBack(String module, CfgCallback callback) {

	}

	@Override
	public void init() {
		if (!inited.getAndSet(true)) {
			try {
				isDebug = LangUtils.parseBoolean(FwUtils.getFwCfg().get(Consts.DEBUG), false);
				RMap<String, Object> rmap = FwUtils.getFwCfg();
				Map<String, Object> cfgMap = new HashMap<>();
				Set<String> set = rmap.keySet();
				for (String key : set) {
					if (key.startsWith(Consts.CFG_DB_PRE)) {
						String tkey = key.substring(7);
						cfgMap.put(tkey, rmap.get(key));
					}
				}
				this.druidDataSource = new DruidDataSource();
				List<Filter> filters = new ArrayList<>();
				Slf4jLogFilter filter = new Slf4jLogFilter();
				filter.setStatementExecutableSqlLogEnable(true);
				filters.add(filter);
				druidDataSource.setProxyFilters(filters);
				DruidDataSourceFactory.config(druidDataSource, cfgMap);
				loadSql();
				loadParam();
				loadValidate();
				loadProp();
				log.info(BusinessConst.LOG_BOOT_REQUEST, "{} classpath started!", ServiceConst.S_CFG_SERVICE);
			} catch (Throwable t) {
				log.info(BusinessConst.LOG_BOOT_REQUEST, ServiceConst.S_CFG_SERVICE + " classpath started!", t);
			}
		}
	}

	@Override
	public void destroy() {
		if (inited.get()) {
			isDebug = true;
			sqlMap.clear();
			paramMap.clear();
			validateMap.clear();
			propMap.clear();
			if (druidDataSource != null) {
				druidDataSource.close();
			}
			log.info(BusinessConst.LOG_BOOT_REQUEST, "{} db stoped!", ServiceConst.S_CFG_SERVICE);
			inited.set(true);
		}
	}

	// ---------------------------------------------------------------------------------
	protected void loadSql() {
		sqlMap.clear();
		String sql = StringUtils.trimNull(FwUtils.getFwCfg().get(Consts.CFG_SQL_ALL));
		String envCode = getSqlEnvCode();
		List<Map<String, Object>> list = listBySql(sql, envCode);
		for (Map<String, Object> map : list) {
			String sqlType = StringUtils.trimNull(map.get(Consts.CFG_SQL_TYPE));
			String busiKey = StringUtils.trimNull(map.get(Consts.CFG_SQL_BUSI_KEY));
			String sqlCode = StringUtils.trimNull(map.get(Consts.CFG_SQL_CODE));
			String sqlValue = StringUtils.trimNull(map.get(Consts.CFG_SQL_VALUE));
			if (sqlType.length() == 0) {
				if (sqlValue.startsWith(Consts.CFG_SQL_TYPE_INSERT)) {
					sqlType = Consts.CFG_SQL_TYPE_INSERT;
				} else if (sqlValue.startsWith(Consts.CFG_SQL_TYPE_UPDATE)) {
					sqlType = Consts.CFG_SQL_TYPE_UPDATE;
				} else if (sqlValue.startsWith(Consts.CFG_SQL_TYPE_DELETE)) {
					sqlType = Consts.CFG_SQL_TYPE_DELETE;
				} else if (sqlValue.startsWith(Consts.CFG_SQL_TYPE_SELECT)) {
					sqlType = Consts.CFG_SQL_TYPE_SELECT;
				}
			}
			map.put(Consts.CFG_SQL_TYPE, sqlType);
			sqlMap.put(busiKey + BaseConst.POINT + sqlCode, map);
		}
	}

	protected void loadSql(String key) {
		String envCode = getSqlEnvCode();
		int lindex = key.lastIndexOf(BaseConst.POINT);
		String busiKey = key.substring(0, lindex);
		String sqlCode = key.substring(lindex + 1);
		String sql = StringUtils.trimNull(FwUtils.getFwCfg().get(Consts.CFG_SQL_ONE));
		Map<String, Object> map = queryBySql(sql, envCode, busiKey, sqlCode);
		if (map != null) {
			String sqlType = StringUtils.trimNull(map.get(Consts.CFG_SQL_TYPE));
			String sqlValue = StringUtils.trimNull(map.get(Consts.CFG_SQL_VALUE));
			if (sqlType.length() == 0) {
				if (sqlValue.startsWith(Consts.CFG_SQL_TYPE_INSERT)) {
					sqlType = Consts.CFG_SQL_TYPE_INSERT;
				} else if (sqlValue.startsWith(Consts.CFG_SQL_TYPE_UPDATE)) {
					sqlType = Consts.CFG_SQL_TYPE_UPDATE;
				} else if (sqlValue.startsWith(Consts.CFG_SQL_TYPE_DELETE)) {
					sqlType = Consts.CFG_SQL_TYPE_DELETE;
				} else if (sqlValue.startsWith(Consts.CFG_SQL_TYPE_SELECT)) {
					sqlType = Consts.CFG_SQL_TYPE_SELECT;
				}
				map.put(Consts.CFG_SQL_TYPE, sqlType);
			}
			sqlMap.put(key, map);
		} else {
			sqlMap.remove(key);
		}
	}

	protected void loadParam() {
		paramMap.clear();
		String sql = StringUtils.trimNull(FwUtils.getFwCfg().get(Consts.CFG_PARAM_ALL));
		String envCode = getParamEnvCode();
		if (sql.length() > 0) {
			List<Map<String, Object>> lists = listBySql(sql, envCode);
			for (Map<String, Object> map : lists) {
				String busiKey = StringUtils.trimNull(map.get(Consts.CFG_PARAM_BUSI_KEY));
				@SuppressWarnings("unchecked")
				List<Map<String, Object>> tempList = (List<Map<String, Object>>) paramMap.get(busiKey);
				if (tempList == null) {
					tempList = new ArrayList<>();
					paramMap.put(busiKey, tempList);
				}
				tempList.add(map);
			}
		}
	}

	protected void loadParam(String key) {
		String sql = StringUtils.trimNull(FwUtils.getFwCfg().get(Consts.CFG_PARAM_ONE));
		String envCode = getParamEnvCode();
		paramMap.remove(key);
		if (sql.length() > 0) {
			List<Map<String, Object>> list = listBySql(sql, envCode, key);
			paramMap.putIfAbsent(key, list);
		}

	}

	protected void loadValidate() {
		validateMap.clear();
		String sql = StringUtils.trimNull(FwUtils.getFwCfg().get(Consts.CFG_VALIDATE_ALL));
		String envCode = getValidateEnvCode();
		if (sql.length() > 0) {
			List<Map<String, Object>> lists = listBySql(sql, envCode);
			for (Map<String, Object> map : lists) {
				String busiKey = StringUtils.trimNull(map.get(Consts.CFG_VALIDATE_BUSI_KEY));
				@SuppressWarnings("unchecked")
				List<Map<String, Object>> tempList = (List<Map<String, Object>>) validateMap.get(busiKey);
				if (tempList == null) {
					tempList = new ArrayList<>();
					validateMap.put(busiKey, tempList);
				}
				tempList.add(map);
			}
		}
	}

	protected void loadValidate(String key) {
		String sql = StringUtils.trimNull(FwUtils.getFwCfg().get(Consts.CFG_VALIDATE_ONE));
		String envCode = getValidateEnvCode();
		validateMap.remove(key);
		if (sql.length() > 0) {
			List<Map<String, Object>> lists = listBySql(sql, envCode, key);
			validateMap.put(key, lists);
		}
	}

	protected void loadProp() {
		propMap.clear();
		String sql = StringUtils.trimNull(FwUtils.getFwCfg().get(Consts.CFG_PROP_ALL));
		String envCode = getPropEnvCode();
		List<Map<String, Object>> list = listBySql(sql, envCode);
		for (Map<String, Object> map : list) {
			String typeCode = StringUtils.trimNull(map.get(Consts.CFG_PROP_TYPE_CODE));
			String itemKey = StringUtils.trimNull(map.get(Consts.CFG_PROP_ITEM_KEY));
			String itemValue = StringUtils.trimNull(map.get(Consts.CFG_PROP_ITEM_VALUE));
			Map<String, Object> rsMap = propMap.get(typeCode);
			if (rsMap == null) {
				rsMap = new HashMap<>();
				propMap.put(typeCode, rsMap);
			}
			if (itemKey.length() > 0) {
				rsMap.put(itemKey, itemValue);
			}

		}
	}

	protected void loadProp(String typeCode) {
		String sql = StringUtils.trimNull(FwUtils.getFwCfg().get(Consts.CFG_PROP_ONE));
		String envCode = getPropEnvCode();
		propMap.remove(typeCode);
		List<Map<String, Object>> list = listBySql(sql, envCode, typeCode);
		if (list.size() > 0) {
			Map<String, Object> rsMap = new HashMap<>();
			for (Map<String, Object> map : list) {
				String itemKey = StringUtils.trimNull(map.get(Consts.CFG_PROP_ITEM_KEY));
				String itemValue = StringUtils.trimNull(map.get(Consts.CFG_PROP_ITEM_VALUE));
				if (itemKey.length() > 0) {
					rsMap.put(itemKey, itemValue);
				}
			}
			propMap.put(typeCode, rsMap);
		}
	}

	protected String getEnvCode() {
		String code = StringUtils.trimNull(FwUtils.getFwCfg().get(Consts.CFG_ENV_CODE), Consts.CFG_ENV_CODE_DEV);
		return code;
	}

	protected String getSqlEnvCode() {
		String code = StringUtils.trimNull(FwUtils.getFwCfg().get(Consts.CFG_ENV_SQL_CODE), getEnvCode());
		return code;
	}

	protected String getParamEnvCode() {
		String code = StringUtils.trimNull(FwUtils.getFwCfg().get(Consts.CFG_ENV_PARAM_CODE), getEnvCode());
		return code;
	}

	protected String getValidateEnvCode() {
		String code = StringUtils.trimNull(FwUtils.getFwCfg().get(Consts.CFG_ENV_VALIDATE_CODE), getEnvCode());
		return code;
	}

	protected String getPropEnvCode() {
		String code = StringUtils.trimNull(FwUtils.getFwCfg().get(Consts.CFG_ENV_PROP_CODE), getEnvCode());
		return code;
	}

	// ---------------------------------------------------------------------------------
	private Map<String, Object> queryBySql(String sql, Object... params) {
		Connection conn = null;
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		try {
			conn = druidDataSource.getConnection();
			pstmt = conn.prepareStatement(sql);
			if (params != null) {
				for (int i = 0; i < params.length; i++) {
					pstmt.setObject(i + 1, params[i]);
				}
			}
			rs = pstmt.executeQuery();
			IResultSetHandler<Map<String, Object>> handler = new RowMapHandler();
			Map<String, Object> rsMap = handler.handle(rs, new HashMap<>());
			return rsMap;
		} catch (Exception e) {
			throw new ServiceException(e.getMessage(), e);
		} finally {
			SqlUtils.closeRs(rs);
			rs = null;
			SqlUtils.closeStmt(pstmt);
			pstmt = null;
			SqlUtils.closeConn(conn);
			conn = null;
		}
	}

	private List<Map<String, Object>> listBySql(String sql, Object... params) {
		Connection conn = null;
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		try {
			conn = druidDataSource.getConnection();
			pstmt = conn.prepareStatement(sql);
			if (params != null) {
				for (int i = 0; i < params.length; i++) {
					pstmt.setObject(i + 1, params[i]);
				}
			}
			rs = pstmt.executeQuery();
			IResultSetHandler<List<Map<String, Object>>> handler = new RowMapListHandler();
			List<Map<String, Object>> list = handler.handle(rs, new HashMap<>());
			return list;
		} catch (Exception e) {
			throw new ServiceException(e.getMessage(), e);
		} finally {
			SqlUtils.closeRs(rs);
			rs = null;
			SqlUtils.closeStmt(pstmt);
			pstmt = null;
			SqlUtils.closeConn(conn);
			conn = null;
		}
	}

}
